// SPDX-FileCopyrightText: 2023 CitruS Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <string>

#include "binder.h"
#include "android_config.h"
#include "android_settings.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "frontend_common/config.h"
#include "native.h"

std::unique_ptr<AndroidConfig> global_config;
std::unique_ptr<AndroidConfig> per_game_config;

template <typename T>
Settings::Setting<T>* getSetting(const std::string& key) {
    auto basic_setting = Settings::values.linkage.by_key[key];
    if (basic_setting != 0) {
        return static_cast<Settings::Setting<T>*>(basic_setting);
    }
    auto basic_android_setting = AndroidSettings::values.linkage.by_key[key];
    if (basic_android_setting != 0) {
        return static_cast<Settings::Setting<T>*>(basic_android_setting);
    }
    LOG_ERROR(Frontend, "[Android Native] Could not find setting - {}", key);
    return nullptr;
}

::ndk::ScopedAStatus Binder::NativeConfig_initializeGlobalConfig() {
    global_config = std::make_unique<AndroidConfig>();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_unloadGlobalConfig() {
    global_config.reset();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_reloadGlobalConfig() {
    global_config->AndroidConfig::ReloadAllValues();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_saveGlobalConfig() {
    global_config->AndroidConfig::SaveAllValues();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_initializePerGameConfig(int64_t programId, const std::string& fileName) {
    auto program_id = static_cast<uint64_t>(programId);
    const auto config_file_name = program_id == 0 ? fileName : fmt::format("{:016X}", program_id);
    per_game_config =
        std::make_unique<AndroidConfig>(config_file_name, Config::ConfigType::PerGameConfig);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_isPerGameConfigLoaded(bool* _aidl_return) {
    *_aidl_return = per_game_config != nullptr;
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_savePerGameConfig() {
    per_game_config->AndroidConfig::SaveAllValues();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_unloadPerGameConfig() {
    per_game_config.reset();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getBoolean(const std::string& key, bool needsGlobal, bool* _aidl_return) {
    auto setting = getSetting<bool>(key);
    *_aidl_return = nullptr != setting && setting->GetValue(needsGlobal);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setBoolean(const std::string& key, bool value) {
    if (auto setting = getSetting<bool>(key))
        setting->SetValue(value);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getByte(const std::string& key, bool needsGlobal, int8_t* _aidl_return) {
    auto setting = getSetting<int8_t>(key);
    *_aidl_return = !setting ? -1 : setting->GetValue(needsGlobal);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setByte(const std::string& key, int8_t value) {
    if (auto setting = getSetting<int8_t>(key))
        setting->SetValue(value);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getShort(const std::string& key, bool needsGlobal, char16_t* _aidl_return) {
    auto setting = getSetting<u16>(key);
    *_aidl_return = !setting ? -1 : setting->GetValue(needsGlobal);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setShort(const std::string& key, char16_t value) {
    if (auto setting = getSetting<u16>(key))
        setting->SetValue(value);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getInt(const std::string& key, bool needsGlobal, int32_t* _aidl_return) {
    auto setting = getSetting<int>(key);
    *_aidl_return = !setting ? -1 : setting->GetValue(needsGlobal);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setInt(const std::string& key, int32_t value) {
    if (auto setting = getSetting<int>(key))
        setting->SetValue(value);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getFloat(const std::string& key, bool needsGlobal, float* _aidl_return) {
    auto setting = getSetting<float>(key);
    *_aidl_return = !setting ? -1.0f : setting->GetValue(needsGlobal);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setFloat(const std::string& key, float value) {
    if (auto setting = getSetting<float>(key))
        setting->SetValue(value);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getLong(const std::string& key, bool needsGlobal, int64_t* _aidl_return) {
    auto setting = getSetting<s64>(key);
    *_aidl_return = !setting ? -1L : setting->GetValue(needsGlobal);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setLong(const std::string& key, int64_t value) {
    if (auto setting = getSetting<s64>(key))
        setting->SetValue(value);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getString(const std::string& key, bool needsGlobal, std::string* _aidl_return) {
    auto setting = getSetting<std::string>(key);
    *_aidl_return = !setting ? "" : setting->GetValue(needsGlobal);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setString(const std::string& key, const std::string& value) {
    if (auto setting = getSetting<std::string>(key))
        setting->SetValue(value);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getIsRuntimeModifiable(const std::string& key, bool* _aidl_return) {
    auto setting = getSetting<std::string>(key);
    *_aidl_return = !setting || setting->RuntimeModifiable();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getPairedSettingKey(const std::string& key, std::string* _aidl_return) {
    if (auto setting = getSetting<std::string>(key))
    {
        if (auto paired = setting->PairedSetting())
        {
            *_aidl_return = paired->GetLabel();
            return ::ndk::ScopedAStatus::ok();
        }
    }
    *_aidl_return = "";
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getIsSwitchable(const std::string& key, bool* _aidl_return) {
    auto setting = getSetting<std::string>(key);
    *_aidl_return = setting != nullptr && setting->Switchable();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_usingGlobal(const std::string& key, bool* _aidl_return) {
    auto setting = getSetting<std::string>(key);
    *_aidl_return = !setting || setting->UsingGlobal();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setGlobal(const std::string& key, bool global) {
    if (auto setting = getSetting<std::string>(key))
        setting->SetGlobal(global);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getIsSaveable(const std::string& key, bool* _aidl_return) {
    auto setting = getSetting<std::string>(key);
    *_aidl_return = setting != nullptr && setting->Save();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getDefaultToString(const std::string& key, std::string* _aidl_return) {
    auto setting = getSetting<std::string>(key);
    *_aidl_return = !setting ? "" : setting->DefaultToString();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getGameDirs(std::vector<IBackend::GameDir>* _aidl_return) {
    *_aidl_return = AndroidSettings::values.game_dirs;
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setGameDirs(const std::vector<IBackend::GameDir>& dirs) {
    AndroidSettings::values.game_dirs = std::move(dirs);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_addGameDir(const IBackend::GameDir& dir) {
    AndroidSettings::values.game_dirs.push_back(dir);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getDisabledAddons(int64_t programId, std::vector<std::string>* _aidl_return) {
    auto program_id = static_cast<uint64_t>(programId);
    *_aidl_return = Settings::values.disabled_addons[program_id];
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setDisabledAddons(int64_t programId, const std::vector<std::string>& disabledAddons) {
    auto program_id = static_cast<uint64_t>(programId);
    Settings::values.disabled_addons[program_id] = std::move(disabledAddons);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getOverlayControlData(std::vector<IBackend::OverlayControlData>* _aidl_return) {
    *_aidl_return = AndroidSettings::values.overlay_control_data;
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setOverlayControlData(const std::vector<IBackend::OverlayControlData>& overlayControlData) {
    AndroidSettings::values.overlay_control_data = std::move(overlayControlData);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_getInputSettings(bool global, std::array<::aidl::com::fanwj::citrus::aidl::IBackend::PlayerInput, 10>* _aidl_return) {
    *_aidl_return = Settings::values.players.GetValue();
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_setInputSettings(const std::array<::aidl::com::fanwj::citrus::aidl::IBackend::PlayerInput, 10>& value, bool global) {
    Settings::values.players.GetValue(global) = std::move(value);
    return ::ndk::ScopedAStatus::ok();
}

::ndk::ScopedAStatus Binder::NativeConfig_saveControlPlayerValues() {
    per_game_config->ClearControlPlayerValues();
    for (size_t index = 0; index < Settings::values.players.GetValue().size(); ++index) {
        per_game_config->SaveAndroidControlPlayerValues(index);
    }
    return ::ndk::ScopedAStatus::ok();
}
